# 指定文件编码为UTF-8
# coding: utf-8

"""
multi_layer_net.py 功能说明：
1. 实现一个全连接的多层神经网络
2. 支持自定义隐藏层数量和每层神经元数量
3. 提供ReLU和Sigmoid两种激活函数选择
4. 实现He和Xavier两种权重初始化方法
5. 支持L2正则化(权重衰减)
6. 提供数值梯度和反向传播两种梯度计算方法
7. 最后一层使用SoftmaxWithLoss实现分类任务

主要功能：
- 前向传播(predict)
- 损失计算(loss)
- 准确率计算(accuracy)
- 梯度计算(numerical_gradient/gradient)
- 支持权重衰减正则化

网络结构示例：
输入层 -> [Affine -> ReLU] x N -> Affine -> SoftmaxWithLoss
"""

# 导入系统模块
import sys, os
# 添加父目录到系统路径
sys.path.append(os.pardir)
# 导入NumPy数值计算库
import numpy as np
# 导入有序字典类
from collections import OrderedDict
# 从common.layers导入网络层
from common.layers import *
# 从common.gradient导入数值梯度计算
from common.gradient import numerical_gradient

class MultiLayerNet:
    """全连接的多层神经网络类

    参数说明:
    ----------
    input_size : 输入层大小(如MNIST为784)
    hidden_size_list : 各隐藏层神经元数量列表(如[100,100,100])
    output_size : 输出层大小(如MNIST为10)
    activation : 激活函数类型('relu'或'sigmoid')
    weight_init_std : 权重初始化标准差
        'relu'或'he' - 使用He初始化
        'sigmoid'或'xavier' - 使用Xavier初始化
    weight_decay_lambda : L2正则化强度系数
    """
    def __init__(self, input_size, hidden_size_list, output_size,
                 activation='relu', weight_init_std='relu', weight_decay_lambda=0):
        self.input_size = input_size
        self.output_size = output_size
        self.hidden_size_list = hidden_size_list
        self.hidden_layer_num = len(hidden_size_list)  # 隐藏层数量
        self.weight_decay_lambda = weight_decay_lambda  # L2正则化系数
        self.params = {}  # 存储网络参数

        # 初始化权重
        self.__init_weight(weight_init_std)

        # 构建网络层
        activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}  # 激活函数层字典
        self.layers = OrderedDict()  # 有序字典存储网络层

        # 添加隐藏层(Affine + Activation)
        for idx in range(1, self.hidden_layer_num+1):
            self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
                                                    self.params['b' + str(idx)])
            self.layers['Activation_function' + str(idx)] = activation_layer[activation]()

        # 添加输出层(Affine)
        idx = self.hidden_layer_num + 1
        self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
                                                self.params['b' + str(idx)])

        # 最后一层(SoftmaxWithLoss)
        self.last_layer = SoftmaxWithLoss()

    def __init_weight(self, weight_init_std):
        """初始化权重参数

        参数:
            weight_init_std : 权重初始化方式
        """
        # 构建各层尺寸列表[输入,隐藏层1,...,输出]
        all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size]

        # 初始化各层权重
        for idx in range(1, len(all_size_list)):
            scale = weight_init_std
            # He初始化(配合ReLU)
            if str(weight_init_std).lower() in ('relu', 'he'):
                scale = np.sqrt(2.0 / all_size_list[idx - 1])
            # Xavier初始化(配合Sigmoid)
            elif str(weight_init_std).lower() in ('sigmoid', 'xavier'):
                scale = np.sqrt(1.0 / all_size_list[idx - 1])

            # 初始化权重和偏置
            self.params['W' + str(idx)] = scale * np.random.randn(
                all_size_list[idx-1], all_size_list[idx])
            self.params['b' + str(idx)] = np.zeros(all_size_list[idx])

    def predict(self, x):
        """前向传播计算输出"""
        for layer in self.layers.values():
            x = layer.forward(x)
        return x

    def loss(self, x, t):
        """计算损失函数值(含L2正则化)"""
        y = self.predict(x)  # 前向传播

        # 计算L2正则化项
        weight_decay = 0
        for idx in range(1, self.hidden_layer_num + 2):
            W = self.params['W' + str(idx)]
            weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)

        # 交叉熵损失 + L2正则化
        return self.last_layer.forward(y, t) + weight_decay

    def accuracy(self, x, t):
        """计算分类准确率"""
        y = self.predict(x)
        y = np.argmax(y, axis=1)  # 获取预测类别
        # 处理one-hot编码的标签
        if t.ndim != 1 : t = np.argmax(t, axis=1)

        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    def numerical_gradient(self, x, t):
        """数值微分法计算梯度"""
        loss_W = lambda W: self.loss(x, t)  # 定义损失函数

        grads = {}
        # 计算各层权重和偏置的梯度
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = numerical_gradient(loss_W, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_W, self.params['b' + str(idx)])

        return grads

    def gradient(self, x, t):
        """反向传播法计算梯度"""
        # 前向传播
        self.loss(x, t)

        # 反向传播
        dout = 1  # 初始化梯度
        dout = self.last_layer.backward(dout)

        # 各层反向传播
        layers = list(self.layers.values())
        layers.reverse()  # 反转层顺序
        for layer in layers:
            dout = layer.backward(dout)

        # 计算并存储梯度(含L2正则化项)
        grads = {}
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = (self.layers['Affine' + str(idx)].dW +
                                   self.weight_decay_lambda * self.layers['Affine' + str(idx)].W)
            grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db

        return grads

"""
使用示例:
1. 初始化网络:
   net = MultiLayerNet(input_size=784, hidden_size_list=[100,100], output_size=10)

2. 前向传播:
   y = net.predict(x)

3. 计算损失:
   loss = net.loss(x, t)

4. 计算准确率:
   acc = net.accuracy(x, t)

5. 计算梯度:
   grads = net.gradient(x, t)  # 反向传播法
   grads = net.numerical_gradient(x, t)  # 数值微分法(用于验证)

6. 参数说明:
   - hidden_size_list: 控制网络深度和宽度
   - activation: 选择激活函数类型
   - weight_decay_lambda: 控制正则化强度
   - weight_init_std: 选择权重初始化方式
"""
